﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Workflows.Components.TextMining.Clustering
{
    /// <summary>
    /// the Tanimoto coefficient is a measure of the similarity of two sets. 
    /// it is used to calculate how similar two items are based on the lists of 
    /// properties
    /// If you have two sets, A and B where 
    ///     A={shirt,shoes,pants,socks}
    ///     B={shirt,skirt,shoes}
    /// then the intersection (overlapping) set, which I'll call C is {shirt,shoes}. 
    /// The Tanimoto coefficient is 2/(4+3-2)=0.4
    /// </summary>
    public class TanimotoCoefficientCalculator:IDistanceCalculator
    {
        #region IDistanceCalculator Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="termFreq1"></param>
        /// <param name="termFreq2"></param>
        /// <param name="contributingTerms"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, DocTermFreq> termFreq1,
            Dictionary<int, DocTermFreq> termFreq2,
            ref Dictionary<int, int> contributingTerms)
        {
            if (termFreq1 == null || termFreq2 == null ||
                termFreq1.Count == 0 || termFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> sharedTermIDs=new List<int>();
            foreach(int termID1 in termFreq1.Keys)
            {
                if(termFreq2.ContainsKey(termID1))
                {
                    sharedTermIDs.Add(termID1);
                    contributingTerms.Add(termID1, Math.Min(termFreq1[termID1].Count, termFreq2[termID1].Count));
                }
            }
            double tanimotoDistance = 1.00 - (double) sharedTermIDs.Count/(termFreq1.Count + termFreq2.Count - sharedTermIDs.Count);
            double contributingTermScale =
                TermWeightCalculator.TaminoCommonTermScale(
                    termFreq1.Count, termFreq2.Count,
                    contributingTerms.Count);
            return tanimotoDistance*contributingTermScale;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="termFreq1"></param>
        /// <param name="termFreq2"></param>
        /// <param name="termWeights"></param>
        /// <param name="useNGramSimilarity"></param>
        /// <param name="contributingTerms"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, DocTermFreq> termFreq1,
            Dictionary<int, DocTermFreq> termFreq2,
            Dictionary<int, double> termWeights,
            bool useNGramSimilarity, int ngramLen,
            ref Dictionary<int, int> contributingTerms)
        {
            if (termFreq1 == null || termFreq2 == null ||
               termFreq1.Count == 0 || termFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> sharedTermIDs = new List<int>();
            Dictionary<int, Dictionary<int, double>> termSimilarityMatrix =
                TermSimilarityMatrix.BuildTermDistanceMatrixUsingNgram(
                    termFreq1, termFreq2);
            List<int> unionTermIDs=new List<int>();
            foreach(int termID in termFreq1.Keys)
            {
                if(!unionTermIDs.Contains(termID))
                    unionTermIDs.Add(termID);
            }
            foreach(int termID in termFreq2.Keys)
            {
                if(!unionTermIDs.Contains(termID))
                    unionTermIDs.Add(termID);
            }

            foreach (int termID in unionTermIDs)
            {
                if (termFreq1.ContainsKey(termID) && termFreq2.ContainsKey(termID))
                {
                    sharedTermIDs.Add(termID);
                    contributingTerms.Add(termID, Math.Min(termFreq1[termID].Count, termFreq2[termID].Count));
                }
                else
                {
                    if (useNGramSimilarity && ngramLen>0)
                    {
                        Dictionary<int, double> similarTerms = termSimilarityMatrix[termID];
                        foreach (int similarTermID in similarTerms.Keys)
                        {
                            if(termFreq1.ContainsKey(termID) && termFreq2.ContainsKey(similarTermID))
                            {
                                if(!sharedTermIDs.Contains(termID))
                                    sharedTermIDs.Add(termID);
                            }
                            else if(termFreq2.ContainsKey(termID) && termFreq1.ContainsKey(similarTermID))
                            {
                                if(!sharedTermIDs.Contains(termID))
                                    sharedTermIDs.Add(termID);
                            }
                        }
                    }
                }
            }
            double tanimotoDistance = 1.00 - (double)sharedTermIDs.Count / (termFreq1.Count + termFreq2.Count - sharedTermIDs.Count);

            //Dictionary<int, double> contributingTermWeights = new Dictionary<int, double>();
            //foreach (int sharedTermID in contributingTerms.Keys)
            //{
            //    if (termWeights.ContainsKey(sharedTermID))
            //    {
            //        contributingTermWeights.Add(sharedTermID, termWeights[sharedTermID]);
            //    }
            //    else
            //    {
            //        contributingTermWeights.Add(sharedTermID, 0);
            //    }
            //}
            double contributingTermScale =
                TermWeightCalculator.TaminoWeightedTermScale(
                    termFreq1, termFreq2,
                    termWeights);

            return tanimotoDistance*contributingTermScale;
        }

        public double CalculateDistance(Dictionary<int, DocTermFreq> termFreq1, Dictionary<int, DocTermFreq> termFreq2,
                                        Dictionary<int, double> termWeights, Dictionary<int, string> termStemmings1,
                                        Dictionary<string, List<int>> termStemmings2,
                                        ref Dictionary<int, int> contributingTerms)
        {
            if (termFreq1 == null || termFreq2 == null ||
              termFreq1.Count == 0 || termFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> sharedTermIDs = new List<int>();
            
            List<int> unionTermIDs = new List<int>();
            foreach (int termID in termFreq1.Keys)
            {
                if (!unionTermIDs.Contains(termID))
                    unionTermIDs.Add(termID);
            }
            foreach (int termID in termFreq2.Keys)
            {
                if (!unionTermIDs.Contains(termID))
                    unionTermIDs.Add(termID);
            }

            foreach (int termID in unionTermIDs)
            {
                if (termFreq1.ContainsKey(termID) && termFreq2.ContainsKey(termID))
                {
                    sharedTermIDs.Add(termID);
                    contributingTerms.Add(termID, Math.Min(termFreq1[termID].Count, termFreq2[termID].Count));
                }
                else
                {
                    List<int> sharedTermIDsAll = termStemmings2[termStemmings1[termID]];
                    List<int> sharedTermIDs1 = new List<int>();
                    List<int> sharedTermIDs2 = new List<int>();
                    foreach (int termID1 in termFreq1.Keys)
                    {
                        if (sharedTermIDsAll.Contains(termID1))
                        {
                            sharedTermIDs1.Add(termID1);
                        }
                    }
                    foreach (int termID2 in termFreq2.Keys)
                    {
                        if (sharedTermIDsAll.Contains(termID2))
                        {
                            sharedTermIDs2.Add(termID2);
                        }
                    }

                    if (sharedTermIDs1.Count>0 && sharedTermIDs2.Count>0)
                    {
                        foreach (int similarTermID in sharedTermIDsAll)
                        {
                            if (!sharedTermIDs.Contains(similarTermID))
                                sharedTermIDs.Add(similarTermID);
                        }
                    }
                }
            }
            double tanimotoDistance = 1.00 - (double)sharedTermIDs.Count / (termFreq1.Count + termFreq2.Count - sharedTermIDs.Count);

            //Dictionary<int, double> contributingTermWeights = new Dictionary<int, double>();
            //foreach (int sharedTermID in contributingTerms.Keys)
            //{
            //    if (termWeights.ContainsKey(sharedTermID))
            //    {
            //        contributingTermWeights.Add(sharedTermID, termWeights[sharedTermID]);
            //    }
            //    else
            //    {
            //        contributingTermWeights.Add(sharedTermID, 0);
            //    }
            //}
            double contributingTermScale =
                TermWeightCalculator.TaminoWeightedTermScale(
                    termFreq1, termFreq2,
                    termWeights);

            return tanimotoDistance * contributingTermScale;
        }

        #endregion
    }
}
